"""Property-based tests for session management functionality.

Feature: buyu-walking-app
Testing correctness properties related to walking session management.
"""
import pytest
import uuid
from hypothesis import given, strategies as st, settings, assume, HealthCheck
from unittest.mock import Mock, patch
from datetime import datetime
from app.models.location import Location
from app.models.route import Route
from app.models.walking_session import WalkingSession
from app.models.user import User
from app.models.poi import POI
from app.services.session_service import SessionService


# Strategies for generating test data
@st.composite
def locations(draw):
    """Generate valid location data."""
    return Location(
        latitude=draw(st.floats(min_value=-90, max_value=90)),
        longitude=draw(st.floats(min_value=-180, max_value=180)),
        altitude=draw(st.one_of(st.none(), st.floats(min_value=-500, max_value=9000))),
        accuracy=draw(st.floats(min_value=0.1, max_value=100))
    )


@st.composite
def user_ids(draw):
    """Generate valid user IDs."""
    return draw(st.text(min_size=1, max_size=36, alphabet=st.characters(
        whitelist_categories=('Lu', 'Ll', 'Nd'), 
        blacklist_characters='-'
    )))


@st.composite
def poi_ids(draw):
    """Generate valid POI IDs."""
    return draw(st.text(min_size=1, max_size=36, alphabet=st.characters(
        whitelist_categories=('Lu', 'Ll', 'Nd'),
        blacklist_characters='-'
    )))


class TestSessionCreationProperty:
    """Property-based tests for session creation."""
    
    @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        user_id=user_ids(),
        poi_id=poi_ids(),
        start_loc=locations(),
        end_loc=locations()
    )
    def test_property_5_session_creation(self, app, user_id, poi_id, start_loc, end_loc):
        """
        Feature: buyu-walking-app, Property 5: 会话创建
        Validates: Requirements 1.5
        
        Property: For any user selection of a POI, the system should create
        a new walking session and start navigation mode.
        
        This tests that:
        1. A session is created for any valid user and POI selection
        2. The session is in 'active' status
        3. The session has all required fields initialized
        """
        with app.app_context():
            # Arrange: Create test user and POI with proper UUIDs
            from app import db
            
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid=f'wx_{uuid.uuid4()}',
                nickname=f'User_{user_id[:8]}'
            )
            db.session.add(user)
            
            poi = POI(
                id=str(uuid.uuid4()),
                name=f'POI_{poi_id[:8]}',
                latitude=end_loc.latitude,
                longitude=end_loc.longitude,
                category='风景名胜'
            )
            db.session.add(poi)
            
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=start_loc.latitude,
                start_longitude=start_loc.longitude,
                end_latitude=end_loc.latitude,
                end_longitude=end_loc.longitude,
                target_poi_id=poi.id,
                distance_meters=1000.0,
                estimated_duration=15
            )
            db.session.add(route)
            db.session.commit()
            
            # Use the actual IDs from the database
            actual_user_id = user.id
            actual_poi_id = poi.id
            
            # Act: Create session
            service = SessionService()
            session = service.create_session(actual_user_id, actual_poi_id, route)
            
            # Assert: Session should be created with correct properties
            assert session is not None, \
                "Session should be created"
            
            assert isinstance(session, WalkingSession), \
                "Created object should be a WalkingSession instance"
            
            assert session.user_id == actual_user_id, \
                f"Session user_id should be {actual_user_id}"
            
            assert session.route_id == route.id, \
                "Session should be linked to the route"
            
            assert session.status == 'active', \
                "New session should have 'active' status"
            
            assert session.start_time is not None, \
                "Session should have a start time"
            
            assert session.end_time is None, \
                "New session should not have an end time"
            
            assert session.current_latitude == start_loc.latitude, \
                "Session should start at route start location (latitude)"
            
            assert session.current_longitude == start_loc.longitude, \
                "Session should start at route start location (longitude)"
            
            assert session.visited_poi_ids == [], \
                "New session should have empty visited POI list"
            
            assert session.generated_music_ids == [], \
                "New session should have empty music list"
            
            assert route.id in session.route_history, \
                "Route should be in session history"
            
            assert session.total_distance == 0.0, \
                "New session should have zero distance"


class TestSessionEndConditionProperty:
    """Property-based tests for session end conditions."""
    
    @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        user_id=user_ids(),
        poi_id=poi_ids(),
        start_loc=locations(),
        end_loc=locations()
    )
    def test_property_7_session_end_condition(self, app, user_id, poi_id, start_loc, end_loc):
        """
        Feature: buyu-walking-app, Property 7: 会话结束条件
        Validates: Requirements 1.7
        
        Property: For any active session, when the user manually stops
        or reaches the endpoint, the session status should change to 'completed'.
        
        This tests that:
        1. Active sessions can be ended
        2. Session status changes to 'completed'
        3. End time is recorded
        4. Summary information is returned
        """
        with app.app_context():
            # Arrange: Create session with proper UUIDs
            from app import db
            
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid=f'wx_{uuid.uuid4()}',
                nickname=f'User_{user_id[:8]}'
            )
            db.session.add(user)
            
            poi = POI(
                id=str(uuid.uuid4()),
                name=f'POI_{poi_id[:8]}',
                latitude=end_loc.latitude,
                longitude=end_loc.longitude,
                category='风景名胜'
            )
            db.session.add(poi)
            
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=start_loc.latitude,
                start_longitude=start_loc.longitude,
                end_latitude=end_loc.latitude,
                end_longitude=end_loc.longitude,
                target_poi_id=poi.id,
                distance_meters=1000.0,
                estimated_duration=15
            )
            db.session.add(route)
            db.session.commit()
            
            actual_user_id = user.id
            actual_poi_id = poi.id
            
            service = SessionService()
            session = service.create_session(actual_user_id, actual_poi_id, route)
            session_id = session.id
            
            # Act: End the session
            summary = service.end_session(session_id)
            
            # Assert: Session should be ended properly
            assert summary is not None, \
                "End session should return a summary"
            
            assert isinstance(summary, dict), \
                "Summary should be a dictionary"
            
            assert summary['status'] == 'completed', \
                "Session status should be 'completed'"
            
            assert summary['session_id'] == session_id, \
                "Summary should contain correct session ID"
            
            assert summary['user_id'] == actual_user_id, \
                "Summary should contain correct user ID"
            
            assert summary['start_time'] is not None, \
                "Summary should contain start time"
            
            assert summary['end_time'] is not None, \
                "Summary should contain end time"
            
            assert summary['duration_seconds'] >= 0, \
                "Duration should be non-negative"
            
            # Verify session in database is updated
            updated_session = WalkingSession.query.get(session_id)
            assert updated_session.status == 'completed', \
                "Session in database should have 'completed' status"
            
            assert updated_session.end_time is not None, \
                "Session in database should have end time"


class TestContinuousLocationMonitoringProperty:
    """Property-based tests for continuous location monitoring."""
    
    @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        user_id=user_ids(),
        poi_id=poi_ids(),
        start_loc=locations(),
        end_loc=locations(),
        location_updates=st.lists(locations(), min_size=1, max_size=10)
    )
    def test_property_8_continuous_location_monitoring(
        self, app, user_id, poi_id, start_loc, end_loc, location_updates
    ):
        """
        Feature: buyu-walking-app, Property 8: 持续位置监测
        Validates: Requirements 2.1, 4.3
        
        Property: For any active walking session, the system should
        continuously receive and process user location updates.
        
        This tests that:
        1. Location updates are accepted for active sessions
        2. Current location is updated in the session
        3. Total distance is accumulated
        4. Multiple updates can be processed sequentially
        """
        with app.app_context():
            # Arrange: Create session with proper UUIDs
            from app import db
            
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid=f'wx_{uuid.uuid4()}',
                nickname=f'User_{user_id[:8]}'
            )
            db.session.add(user)
            
            poi = POI(
                id=str(uuid.uuid4()),
                name=f'POI_{poi_id[:8]}',
                latitude=end_loc.latitude,
                longitude=end_loc.longitude,
                category='风景名胜'
            )
            db.session.add(poi)
            
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=start_loc.latitude,
                start_longitude=start_loc.longitude,
                end_latitude=end_loc.latitude,
                end_longitude=end_loc.longitude,
                target_poi_id=poi.id,
                distance_meters=1000.0,
                estimated_duration=15
            )
            db.session.add(route)
            db.session.commit()
            
            actual_user_id = user.id
            actual_poi_id = poi.id
            
            service = SessionService()
            session = service.create_session(actual_user_id, actual_poi_id, route)
            session_id = session.id
            
            # Act: Update location multiple times
            for location in location_updates:
                service.update_session_location(session_id, location)
            
            # Assert: Session should reflect all updates
            updated_session = WalkingSession.query.get(session_id)
            
            assert updated_session is not None, \
                "Session should still exist after updates"
            
            assert updated_session.status == 'active', \
                "Session should remain active during location updates"
            
            # Last location should be current
            last_location = location_updates[-1]
            assert updated_session.current_latitude == last_location.latitude, \
                "Current latitude should match last update"
            
            assert updated_session.current_longitude == last_location.longitude, \
                "Current longitude should match last update"
            
            # Distance should be accumulated (non-negative)
            assert updated_session.total_distance >= 0, \
                "Total distance should be non-negative"
            
            # If we had multiple updates, distance should likely be > 0
            # (unless all locations are identical, which is unlikely with random data)
            if len(location_updates) > 1:
                # Check that at least some locations are different
                locations_differ = any(
                    location_updates[i].latitude != location_updates[i+1].latitude or
                    location_updates[i].longitude != location_updates[i+1].longitude
                    for i in range(len(location_updates) - 1)
                )
                
                if locations_differ:
                    assert updated_session.total_distance > 0, \
                        "Total distance should increase with different locations"


class TestLocationTrackingStopProperty:
    """Property-based tests for location tracking stop."""
    
    @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        user_id=user_ids(),
        poi_id=poi_ids(),
        start_loc=locations(),
        end_loc=locations()
    )
    def test_property_20_location_tracking_stop(self, app, user_id, poi_id, start_loc, end_loc):
        """
        Feature: buyu-walking-app, Property 20: 位置追踪停止
        Validates: Requirements 4.5
        
        Property: For any ended walking session, the system should stop
        continuous location tracking.
        
        This tests that:
        1. After ending a session, location updates should not be accepted
        2. Attempting to update location on completed session raises error
        3. Session remains in completed state
        """
        with app.app_context():
            # Arrange: Create and end session with proper UUIDs
            from app import db
            
            user = User(
                id=str(uuid.uuid4()),
                wechat_openid=f'wx_{uuid.uuid4()}',
                nickname=f'User_{user_id[:8]}'
            )
            db.session.add(user)
            
            poi = POI(
                id=str(uuid.uuid4()),
                name=f'POI_{poi_id[:8]}',
                latitude=end_loc.latitude,
                longitude=end_loc.longitude,
                category='风景名胜'
            )
            db.session.add(poi)
            
            route = Route(
                id=str(uuid.uuid4()),
                start_latitude=start_loc.latitude,
                start_longitude=start_loc.longitude,
                end_latitude=end_loc.latitude,
                end_longitude=end_loc.longitude,
                target_poi_id=poi.id,
                distance_meters=1000.0,
                estimated_duration=15
            )
            db.session.add(route)
            db.session.commit()
            
            actual_user_id = user.id
            actual_poi_id = poi.id
            
            service = SessionService()
            session = service.create_session(actual_user_id, actual_poi_id, route)
            session_id = session.id
            
            # End the session
            service.end_session(session_id)
            
            # Act & Assert: Attempt to update location should fail
            # Ensure new location is within valid bounds
            new_lat = min(89.0, max(-89.0, start_loc.latitude + 0.01))
            new_lon = min(179.0, max(-179.0, start_loc.longitude + 0.01))
            new_location = Location(
                latitude=new_lat,
                longitude=new_lon,
                accuracy=10.0
            )
            
            with pytest.raises(ValueError) as exc_info:
                service.update_session_location(session_id, new_location)
            
            assert 'completed' in str(exc_info.value).lower(), \
                "Error message should indicate session is completed"
            
            # Verify session remains completed
            final_session = WalkingSession.query.get(session_id)
            assert final_session.status == 'completed', \
                "Session should remain in completed state"



